﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using Phoenix.ComponentModel;
using Phoenix.Events;
using Phoenix.Exceptions;
using Phoenix.Frames;
using Phoenix.Framework;
using Phoenix.Lifetime;
using Phoenix.Storage;
using Phoenix.Utilities;

namespace Phoenix
{
    // ReSharper disable PartialTypeWithSinglePart
    public abstract partial class ViewModelBase : NotifyPropertyChanged, IActivationAware
    {
        readonly IList<PropertyInfo> transientProperties = new List<PropertyInfo>();
        readonly IList<PropertyInfo> applicationTransientProperties = new List<PropertyInfo>();

        readonly List<Action<RequestClosingEventArgs>> closingTasks = new List<Action<RequestClosingEventArgs>>();
        readonly List<Action> cleanupTasks = new List<Action>();
        readonly List<Action> activateTasks = new List<Action>();
        bool activated;

        public ControllerActionContext Context { get; internal set; }

        /// <summary>
        /// Helper method to flow Context and lifecycle events to viewmodels which are not returned from controller actions 
        /// (i.e, you create the viewmodel inside a viewmodel)
        /// </summary>
        protected void RegisterChildViewModel<TViewModel>(TViewModel viewModel) where TViewModel : ViewModelBase
        {
            viewModel.Context = Context;
            viewModel.PageState = PageState;
            viewModel.ApplicationState = ApplicationState;
            viewModel.EventAggregator = EventAggregator;

            AddSetupTask(() => viewModel.Activated(this, EventArgs.Empty));
            AddCleanupTask(() => viewModel.Deactivated(this, EventArgs.Empty));
            AddClosingTask(e => viewModel.Closing(this, e));
        }

        /// <summary>
        /// Used to invoke Actions on a specific controller type, allowing type safe invocation
        /// </summary>
        /// <typeparam name="TController"></typeparam>
        /// <returns></returns>
        public InvokeActionBuilder<TController> Controller<TController>()
            where TController : Controller
        {
            return new InvokeActionBuilder<TController>(this);
        }

        /// <summary>
        /// Used to invoke Controller Actions
        /// </summary>
        public InvokeActionBuilder InvokeActionBuilder
        {
            get
            {
                return new InvokeActionBuilder(this);                
            }
        }

        public void Activated(object sender, EventArgs e)
        {
            try
            {
                foreach (var activateTask in activateTasks)
                    activateTask();
                activated = true;

                foreach (var property in transientProperties)
                    HydrateProperty(property);
                foreach (var property in applicationTransientProperties)
                    HydrateProperty(property);

                OnActivated();
            }
            catch (AggregateException ex)
            {
                var aggregateException = ex.Flatten();
                Context.Host.ErrorReporter.ReportException(aggregateException, "ViewModel activation failed", true);
                throw new ViewModelActivationFailedException(GetType().Name, aggregateException);
            }
            catch (Exception ex)
            {
                Context.Host.ErrorReporter.ReportException(ex, "ViewModel activation failed", true);
                throw new ViewModelActivationFailedException(GetType().Name, ex);
            }
        }

        public void Deactivated(object sender, EventArgs e)
        {
            try
            {
                OnDeactivated();

                foreach (var cleanupTask in cleanupTasks)
                    cleanupTask();
                activated = false;

                foreach (var property in transientProperties)
                    PersistProperty(property);
                foreach (var property in applicationTransientProperties)
                    PersistProperty(property);
            }
            catch (AggregateException ex)
            {
                var aggregateException = ex.Flatten();
                Context.Host.ErrorReporter.ReportException(aggregateException, "ViewModel deactivation failed", true);
                throw new ViewModelDeactivationFailedException(GetType().Name, aggregateException);
            }
            catch (Exception ex)
            {
                Context.Host.ErrorReporter.ReportException(ex, "ViewModel deactivation failed", true);
                throw new ViewModelDeactivationFailedException(GetType().Name, ex);
            }
        }

        public void Closing(object sender, RequestClosingEventArgs eventArgs)
        {
            OnClosing(eventArgs);

            foreach (var closingTask in closingTasks)
                closingTask(eventArgs);
        }

        protected void Transient<T>(Expression<Func<T>> propertySelector)
        {
            transientProperties.Add(PropertyResolver.PropertyFor(propertySelector));
        }

        protected void ApplicationTransient<T>(Expression<Func<T>> propertySelector)
        {
            applicationTransientProperties.Add(PropertyResolver.PropertyFor(propertySelector));
        }

        protected void HydrateProperty(PropertyInfo property)
        {
            var propertyKey = string.Format("{0}.{1}", GetType().FullName, property.Name);
            if (!PageState.Contains(propertyKey)) return;

            var value = PageState.Load<TransientProperty>(propertyKey);
            property.SetValue(this, value.Value, null);
        }

        protected void PersistProperty(PropertyInfo property)
        {
            var propertyKey = string.Format("{0}.{1}", GetType().FullName, property.Name);
            var value = property.GetValue(this, null);
            PageState.Save(propertyKey, new TransientProperty { Value = value });
        }

        /// <summary>
        /// Invoked when the viewmodel has been activated
        /// </summary>
        protected virtual void OnActivated()
        {

        }

        /// <summary>
        /// Invoked when the viewmodel is about to be deactivated
        /// </summary>
        protected virtual void OnDeactivated()
        {

        }

        /// <summary>
        /// Invoked when the viewmodel can 
        /// </summary>
        protected virtual void OnClosing(RequestClosingEventArgs eventArgs)
        {
        }

        protected void NavigateBack()
        {
            Context.NavigationFrame.NavigateBack();
        }

        protected void NavigateBackTo(string controller, string action)
        {
            Context.NavigationFrame.NavigateBackTo(controller, action);
        }

        internal void AddSetupTask(Action action)
        {
           if (activated)
                action();
            activateTasks.Add(action);
        }

        internal void AddCleanupTask(Action action)
        {
            cleanupTasks.Add(action);
        }

        void AddClosingTask(Action<RequestClosingEventArgs> action)
        {
            closingTasks.Add(action);
        }

        public IRequestTransientStore PageState { get; internal set; }
        public ITransientStore ApplicationState { get; internal set; }
        public IEventAggregator EventAggregator { get; internal set; }
    }
    // ReSharper restore PartialTypeWithSinglePart
}